﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using System.IO;
using System.Threading;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using Redemption;

namespace WLMToPst
{
    namespace Redemption
    {
        public static class RedemptionLoader
        {
            #region public methods
            //64 bit dll location - defaults to <assemblydir>\Redemption64.dll
            public static string DllLocation64Bit;
            //32 bit dll location - defaults to <assemblydir>\Redemption.dll
            public static string DllLocation32Bit;


            //The only creatable RDO object - RDOSession
            public static RDOSession new_RDOSession()
            {
                return (RDOSession)NewRedemptionObject(new Guid("29AB7A12-B531-450E-8F7A-EA94C2F3C05F"));
            }

            //Safe*Item objects
            public static SafeMailItem new_SafeMailItem()
            {
                return (SafeMailItem)NewRedemptionObject(new Guid("741BEEFD-AEC0-4AFF-84AF-4F61D15F5526"));
            }

            public static SafeContactItem new_SafeContactItem()
            {
                return (SafeContactItem)NewRedemptionObject(new Guid("4FD5C4D3-6C15-4EA0-9EB9-EEE8FC74A91B"));
            }

            public static SafeAppointmentItem new_SafeAppointmentItem()
            {
                return (SafeAppointmentItem)NewRedemptionObject(new Guid("620D55B0-F2FB-464E-A278-B4308DB1DB2B"));
            }

            public static SafeTaskItem new_SafeTaskItem()
            {
                return (SafeTaskItem)NewRedemptionObject(new Guid("7A41359E-0407-470F-B3F7-7C6A0F7C449A"));
            }

            public static SafeJournalItem new_SafeJournalItem()
            {
                return (SafeJournalItem)NewRedemptionObject(new Guid("C5AA36A1-8BD1-47E0-90F8-47E7239C6EA1"));
            }

            public static SafeMeetingItem new_SafeMeetingItem()
            {
                return (SafeMeetingItem)NewRedemptionObject(new Guid("FA2CBAFB-F7B1-4F41-9B7A-73329A6C1CB7"));
            }

            public static SafePostItem new_SafePostItem()
            {
                return (SafePostItem)NewRedemptionObject(new Guid("11E2BC0C-5D4F-4E0C-B438-501FFE05A382"));
            }

            public static SafeReportItem new_SafeReportItem()
            {
                return (SafeReportItem)NewRedemptionObject(new Guid("D46BA7B2-899F-4F60-85C7-4DF5713F6F18"));
            }

            public static MAPIFolder new_MAPIFolder()
            {
                return (MAPIFolder)NewRedemptionObject(new Guid("03C4C5F4-1893-444C-B8D8-002F0034DA92"));
            }

            public static SafeCurrentUser new_SafeCurrentUser()
            {
                return (SafeCurrentUser)NewRedemptionObject(new Guid("7ED1E9B1-CB57-4FA0-84E8-FAE653FE8E6B"));
            }

            public static SafeDistList new_SafeDistList()
            {
                return (SafeDistList)NewRedemptionObject(new Guid("7C4A630A-DE98-4E3E-8093-E8F5E159BB72"));
            }

            public static AddressLists new_AddressLists()
            {
                return (AddressLists)NewRedemptionObject(new Guid("37587889-FC28-4507-B6D3-8557305F7511"));
            }

            public static MAPITable new_MAPITable()
            {
                return (MAPITable)NewRedemptionObject(new Guid("A6931B16-90FA-4D69-A49F-3ABFA2C04060"));
            }

            public static MAPIUtils new_MAPIUtils()
            {
                return (MAPIUtils)NewRedemptionObject(new Guid("4A5E947E-C407-4DCC-A0B5-5658E457153B"));
            }

            public static SafeInspector new_SafeInspector()
            {
                return (SafeInspector)NewRedemptionObject(new Guid("ED323630-B4FD-4628-BC6A-D4CC44AE3F00"));
            }

            public static SafeExplorer new_SafeExplorer()
            {
                return (SafeExplorer)NewRedemptionObject(new Guid("C3B05695-AE2C-4FD5-A191-2E4C782C03E0"));
            }

            #endregion


            #region private methods



            static RedemptionLoader()
            {
                //default locations of the dlls

                //use CodeBase instead of Location because of Shadow Copy.
                string codebase = Assembly.GetExecutingAssembly().CodeBase;
                var vUri = new UriBuilder(codebase);
                string vPath = Uri.UnescapeDataString(vUri.Path + vUri.Fragment);
                string directory = Path.GetDirectoryName(vPath);
                if (!string.IsNullOrEmpty(vUri.Host)) directory = @"\\" + vUri.Host + directory;
                DllLocation64Bit = Path.Combine(directory, "redemption64.dll");
                DllLocation32Bit = Path.Combine(directory, "redemption.dll");
                bool fileExists32 = File.Exists(DllLocation32Bit);
                bool fileExists64 = File.Exists(DllLocation64Bit);
                bool fileExists64a = File.Exists(DllLocation64Bit);
            }



            /*
            static ~Loader()
            {
                if (!_redemptionDllHandle.Equals(IntPtr.Zero))
                {
                    IntPtr dllCanUnloadNowPtr = Win32NativeMethods.GetProcAddress(_redemptionDllHandle, "DllCanUnloadNow");
                    if (!dllCanUnloadNowPtr.Equals(IntPtr.Zero))
                    {
                        DllCanUnloadNow dllCanUnloadNow = (DllCanUnloadNow)Marshal.GetDelegateForFunctionPointer(dllCanUnloadNowPtr, typeof(DllCanUnloadNow));
                        if (dllCanUnloadNow() != 0) return; //there are still live objects returned by the dll, so we should not unload the dll
                    }
                    Win32NativeMethods.FreeLibrary(_redemptionDllHandle);
                    _redemptionDllHandle = IntPtr.Zero;
                }
            }
            */


            [ComVisible(false)]
            [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00000001-0000-0000-C000-000000000046")]
            private interface IClassFactory
            {
                void CreateInstance([MarshalAs(UnmanagedType.Interface)] object pUnkOuter, ref Guid refiid, [MarshalAs(UnmanagedType.Interface)] out object ppunk);
                void LockServer(bool fLock);
            }

            [ComVisible(false)]
            [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00000000-0000-0000-C000-000000000046")]
            private interface IUnknown
            {
            }

            private delegate int DllGetClassObject(ref Guid ClassId, ref Guid InterfaceId, [Out, MarshalAs(UnmanagedType.Interface)] out object ppunk);
            private delegate int DllCanUnloadNow();

            //COM GUIDs
            private static Guid IID_IClassFactory = new Guid("00000001-0000-0000-C000-000000000046");
            private static Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046");

            //win32 functions to load\unload dlls and get a function pointer 
            private class Win32NativeMethods
            {
                [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
                public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

                [DllImport("kernel32.dll", SetLastError = true)]
                public static extern bool FreeLibrary(IntPtr hModule);

                [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
                public static extern IntPtr LoadLibraryW(string lpFileName);
            }

            //private variables
            private static IntPtr _redemptionDllHandle = IntPtr.Zero;
            private static IntPtr _dllGetClassObjectPtr = IntPtr.Zero;
            private static DllGetClassObject _dllGetClassObject;
            private static readonly object _criticalSection = new object();

            private static IUnknown NewRedemptionObject(Guid guid)
            {
                //try to set the thread COM apartment
                //Thread.CurrentThread.ApartmentState = ApartmentState.STA;

                object res = null;
                Trace.WriteLine($"starting NewRedemptionObject: {guid.ToString()}");

                lock (_criticalSection)
                {
                    IClassFactory ClassFactory;
                    if (_redemptionDllHandle.Equals(IntPtr.Zero))
                    {
                        string dllPath;
                        if (IntPtr.Size == 8)
                        {
                            dllPath = DllLocation64Bit;
                            Trace.WriteLine($"DllLocation64Bit: '{DllLocation64Bit}'");
                        }
                        else
                        {
                            dllPath = DllLocation32Bit;
                            Trace.WriteLine($"DllLocation32Bit: '{DllLocation32Bit}'");
                        }
                        _redemptionDllHandle = Win32NativeMethods.LoadLibraryW(dllPath);
                        if (_redemptionDllHandle.Equals(IntPtr.Zero))
                            //throw new Exception(string.Format("Could not load '{0}'\nMake sure the dll exists.", dllPath));
                            throw new Win32Exception(Marshal.GetLastWin32Error());
                        _dllGetClassObjectPtr = Win32NativeMethods.GetProcAddress(_redemptionDllHandle, "DllGetClassObject");
                        if (_dllGetClassObjectPtr.Equals(IntPtr.Zero))
                            //throw new Exception("Could not retrieve a pointer to the 'DllGetClassObject' function exported by the dll");
                            throw new Win32Exception(Marshal.GetLastWin32Error());
                        _dllGetClassObject =
                            (DllGetClassObject)
                            Marshal.GetDelegateForFunctionPointer(_dllGetClassObjectPtr, typeof(DllGetClassObject));
                        Trace.WriteLine($"_dllGetClassObject is loaded!: '{_dllGetClassObject.ToString()}'");
                    } else {
                        Trace.WriteLine($"_redemptionDllHandle.Equals(IntPtr.Zero) is NOT");
                    }

                    Object unk;
                    int hr = _dllGetClassObject(ref guid, ref IID_IClassFactory, out unk);
                    Trace.WriteLine($"hr: {hr.ToString()}");
                    if (hr != 0)
                    {
                        throw new Exception("DllGetClassObject failed with error code 0x" + hr.ToString("x8"));
                    }
                    ClassFactory = unk as IClassFactory;
                    Trace.WriteLine($"ClassFactory: {ClassFactory.ToString()}");
                    Trace.WriteLine($"IID_IUnknown: {IID_IUnknown.ToString()}");
                    ClassFactory.CreateInstance(null, ref IID_IUnknown, out res); //stuck here
                    Trace.WriteLine($"ClassFactory.CreateInstance created: {res.ToString()}");
                    //If the same class factory is returned as the one still
                    //referenced by .Net, the call will be marshalled to the original thread
                    //where that class factory was retrieved first.
                    //Make .Net forget these objects
                    Marshal.ReleaseComObject(unk);
                    Trace.WriteLine($"Marshal.ReleaseComObject(unk);");
                    Marshal.ReleaseComObject(ClassFactory);
                    Trace.WriteLine($"Marshal.ReleaseComObject(ClassFactory);");

                    Trace.WriteLine($"seems all loaded good: {IID_IUnknown.ToString()}");
                } //lock

                
                Trace.Flush(); // Flush the output.
                return (res as IUnknown);
            }

            #endregion

        }

        /*
        public class RDOSession : RDOSessionClass 
        {
            static RDOSession()
            {
                ExtensibleClassFactory.RegisterObjectCreationCallback(callbackCreate_RDOSession);
            }
            static private IntPtr callbackCreate_RDOSession(IntPtr aggregator)
            {
                object o = RedemptionLoader.new_RDOSession();
                return Marshal.GetIUnknownForObject(o);
            }
        }
        */
    }


}